package org.aspire.graphics.display;

import java.util.ArrayList;

import org.aspire.core.Config;
import org.aspire.core.config.PCConfig;
import org.aspire.data.Owner;
import org.aspire.graphics.DisplayView;
import org.aspire.graphics.DrawGraphics;
import org.aspire.graphics.draw.OpenGLGraphics;
import org.aspire.math.geom.Dimension;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;

/******************************************************************************
 * A display view implementation for OpenGL. It takes advantage of LWJGL
 * interface for rendering graphics in OpenGL.
 * 
 * @author Golden T Studios (GTGE)
 * @author JPACarabuena
 *****************************************************************************/
public class OpenGLDisplay implements DisplayView {

	// ========================================================================
	// Constants
	// ========================================================================

	// ========================================================================
	// Static Methods
	// ========================================================================

	// ========================================================================
	// Fields
	// ========================================================================
	
	/**************************************************************************
	 * The screen size of the display view.
	 *************************************************************************/
	protected Dimension displaySize;
	
	/**************************************************************************
	 * The owner of this display view.
	 *************************************************************************/
	protected Owner owner;
	
	/**************************************************************************
	 * The window listener for this display view.
	 *************************************************************************/
	protected PCWindowListener listener;
	
	/**************************************************************************
	 * Whether or not OpenGL will run in full-screen mode.
	 *************************************************************************/
	protected boolean fullscreen;
	
	/**************************************************************************
	 * Whether or not VSync will be used.
	 *************************************************************************/
	protected boolean vsync;
	
	/**************************************************************************
	 * The draw graphics for this display view.
	 *************************************************************************/
	protected OpenGLGraphics currentGraphics;

	// ========================================================================
	// Constructors
	// ========================================================================

	/**************************************************************************
	 * Construct a new OpenGL display view with the specified game
	 * configurations.
	 * 
	 * @param owner		The owner of this display view.
	 * @param config	The game configuration.
	 *************************************************************************/
	public OpenGLDisplay(Owner owner, PCConfig config) {
		this.owner = owner;
		
		this.displaySize = config.getScreenDisplaySize();
		
		this.fullscreen = config.isFullScreenExclusiveModeEnabled();
		this.vsync = config.isVSyncEnabled();

		this.initGraphics(config);
		this.initGL();
		
		this.listener = new PCWindowListener();
		
		this.currentGraphics = new OpenGLGraphics();
	}
	
	// ========================================================================
	// Getters & Setters
	// ========================================================================

	// ========================================================================
	// Override Methods
	// ========================================================================

	@Override
	public int getWidth() {
		return this.displaySize.getWidthInt();
	}

	@Override
	public int getHeight() {
		return this.displaySize.getHeightInt();
	}

	@Override
	public void setWidth(int width) {
		// do nothing
	}

	@Override
	public void setHeight(int height) {
		// do nothing
	}
	
	@Override
	public Owner getOwner() {
		return this.owner;
	}

	@Override
	public DrawGraphics getBackBuffer() {
		return this.currentGraphics;
	}

	@Override
	public boolean flip() {
		if(Display.isCloseRequested()) {
			listener.windowClosing(null);
		} else if(Display.isActive()) {
			Display.update();
		} else {
			if(Display.isVisible() || Display.isDirty()) {
				Display.update();
			}
		}
		
		return true;
	}

	@Override
	public void cleanup() {
		try {
			Display.destroy();
		} catch(Exception e) {
			System.err.println("ERROR: Shutting down graphics context " + e);
			System.exit(-1);
		}
	}

	// ========================================================================
	// Methods
	// ========================================================================

	/**************************************************************************
	 * Initialize OpenGL frame. This will create a display window using OpenGL
	 * functionalities through LWJGL.
	 * 
	 * @param config	the game configuration.
	 *************************************************************************/
	public void initGraphics(Config config) {
		try {
			DisplayMode best = (this.fullscreen) ? 
					this.getBestDisplayMode(this.displaySize) :
						new DisplayMode(this.getWidth(), this.getHeight());
					
			if(best == null) {
				throw new Exception("DisplayMode Not Supported");
			}
			
			Display.setDisplayMode(best);
		} catch(Exception e) {
			e.printStackTrace();
			
			throw new RuntimeException("LWJGL Error: Unable to set desired" +
					" display mode(" + this.getWidth() + "x" + 
					this.getHeight() + "x" + 16 + ")");
		}
		
		try {
			Display.setTitle(config.getGameTitle());
			Display.setFullscreen(fullscreen);
			Display.setVSyncEnabled(vsync);
			Display.create();
		} catch(Exception e) {
			e.printStackTrace();
			
			throw new RuntimeException("LWJGL Error: Unable to initialize" +
					" display");
		}
	}
	
	/**************************************************************************
	 * Initialize OpenGL rendering option. This will configure OpenGL for
	 * rendering 2D games.
	 *************************************************************************/
	public void initGL() {
		/* Enable Texture for Sprite */
		GL11.glEnable(GL11.GL_TEXTURE_2D);
		
		/* Disable Depth Test for 2D Graphics */
		GL11.glDisable(GL11.GL_DEPTH_TEST);
		
		GL11.glMatrixMode(GL11.GL_PROJECTION);
		GL11.glLoadIdentity();
		
		GL11.glOrtho(0, this.displaySize.getWidth(), 
				this.displaySize.getHeight(), 0, -1, 1);
		
		/* Enable Transparency */
		GL11.glEnable(GL11.GL_BLEND);
		GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
	}

	/**************************************************************************
	 * Gets the best display mode given the specified size.
	 * 
	 * @param size	the screen size of the display view.
	 * @return		the best display mode.
	 *************************************************************************/
	public DisplayMode getBestDisplayMode(Dimension size) {
		try {
			DisplayMode modes[] = Display.getAvailableDisplayModes();
			
			/* Looks for DisplayMode with the same dimension as the game */
			ArrayList<DisplayMode> matchModes = new ArrayList<DisplayMode>();
			for(int i=0; i<modes.length; i++) {
				if(modes[i].getWidth() == this.getWidth() 
						&& modes[i].getHeight() == this.getHeight()) {
					matchModes.add(modes[i]);
				}
			}
			
			/* The Best DisplayMode is usually the last entry of the Array */
			DisplayMode matchModesArray[] = matchModes.toArray(new DisplayMode[0]);
			DisplayMode best = matchModesArray.length==0 ? 
					null : matchModesArray[matchModesArray.length-1];
		
			return best;
		} catch(Exception e) {
			return null;
		}
	}

	// ========================================================================
	// Inner & Anonymous Classes
	// ========================================================================
}